package com.micro.service.auth.server.config.oauth2;


import com.micro.service.auth.server.config.security.MyUserDetailsServiceImpl;
import com.micro.service.auth.server.utils.AsyncThreadPool;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.context.annotation.Primary;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.approval.UserApprovalHandler;
import org.springframework.security.oauth2.provider.error.WebResponseExceptionTranslator;
import org.springframework.security.oauth2.provider.request.DefaultOAuth2RequestFactory;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenStore;


/**
 * @author lvxiucai
 * @description
 * @date 2019/7/31
 */
@Configuration
//@AutoConfigureAfter(MyWebSecurityConfig.class)
@DependsOn({"myWebSecurityConfig"})
@EnableAuthorizationServer
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class AuthServerConfig  extends AuthorizationServerConfigurerAdapter {

    @Autowired
    private TokenStore tokenStore;

    @Autowired
    private UserApprovalHandler userApprovalHandler;

    @Autowired
    private CustomClientDetailsService clientDetailsService;

    @Autowired
    private MyUserDetailsServiceImpl userService;

    @Autowired
    private AuthenticationManager authenticationManager;

    @Value("${spring.redis.defaultExpireTime}")
    private int defaultExpireTime;


    @Autowired
    private AsyncThreadPool asyncThreadPool;


    /**
     * @Description: access_token 永不失效设置方法
     * @author lv
     * @create 17:09 2018/3/13
     * @param
     * @return org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices
     */
    @Primary
    @Bean
    public AuthorizationServerTokenServices tokenServices() {
        DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
        defaultTokenServices.setAccessTokenValiditySeconds(defaultExpireTime);
        defaultTokenServices.setRefreshTokenValiditySeconds(2*defaultExpireTime);
        defaultTokenServices.setSupportRefreshToken(true);
        defaultTokenServices.setReuseRefreshToken(false);
        defaultTokenServices.setTokenStore(tokenStore);
        defaultTokenServices.setTokenEnhancer(customTokenEnhancer());
        defaultTokenServices.setClientDetailsService(clientDetailsService);
        return defaultTokenServices;
    }

    @Bean
    public CustomTokenEnhancer customTokenEnhancer(){return new CustomTokenEnhancer();};

    /**
     * @Description: 用来配置授权（authorization）以及令牌（token）的访问端点和令牌服务(token services)
     * authenticationManager：认证管理器，当你选择了资源所有者密码（password）授权类型的时候，请设置这个属性注入一个 AuthenticationManager 对象。
     * userDetailsService：如果啊，你设置了这个属性的话，那说明你有一个自己的 UserDetailsService 接口的实现，或者你可以把这个东西设置到全局域上面去（例如 GlobalAuthenticationManagerConfigurer 这个配置对象），当你设置了这个之后，那么 "refresh_token" 即刷新令牌授权类型模式的流程中就会包含一个检查，用来确保这个账号是否仍然有效，假如说你禁用了这个账户的话。
     * authorizationCodeServices：这个属性是用来设置授权码服务的（即 AuthorizationCodeServices 的实例对象），主要用于 "authorization_code" 授权码类型模式。
     * implicitGrantService：这个属性用于设置隐式授权模式，用来管理隐式授权模式的状态。
     * tokenGranter：这个属性就很牛B了，当你设置了这个东西（即 TokenGranter 接口实现），
     * 那么授权将会交由你来完全掌控，并且会忽略掉上面的这几个属性，这个属性一般是用作拓展用途的，
     * 即标准的四种授权模式已经满足不了你的需求的时候，才会考虑使用这个。
     * @author lv
     * @create 16:11 2017/12/28
     * @last modify by [lv 16:11 2017/12/28 ]
     * @param endpoints
     * @return void
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        CustomRefreshTokenGranter refreshTokenGranter = new CustomRefreshTokenGranter(tokenServices(),clientDetailsService,new DefaultOAuth2RequestFactory(clientDetailsService));
        refreshTokenGranter.setAsyncThreadPool(asyncThreadPool);


        endpoints
                .authenticationManager(authenticationManager)
                .userDetailsService(userService)//若无，refresh_token会有UserDetailsService is required错误
                .userApprovalHandler(userApprovalHandler)
                .tokenStore(tokenStore)
                .tokenEnhancer(customTokenEnhancer())//加强器，添加用户其余信息
                .tokenGranter(refreshTokenGranter)
                .pathMapping("/oauth/confirm_access","/custom/confirm_access")
                .allowedTokenEndpointRequestMethods(HttpMethod.GET,HttpMethod.POST)
                .tokenServices(tokenServices()).exceptionTranslator(webResponseExceptionTranslator());
        //自定义授权页面
//        endpoints.pathMapping("/oauth/confirm_access","/custom/confirm_access");

    }
    @Bean
    public WebResponseExceptionTranslator webResponseExceptionTranslator() {
        return new CustomerWebResponseExceptionTranslator();
    }
    /**
     * @Description: 用来配置令牌端点(Token Endpoint)的安全约束
     * Token令牌默认是有签名的，并且资源服务需要验证这个签名，
     * 因此呢，你需要使用一个对称的Key值，用来参与签名计算，
     * 这个Key值存在于授权服务以及资源服务之中。
     * 或者你可以使用非对称加密算法来对Token进行签名，
     * Public Key公布在/oauth/token_key这个URL连接中，
     * 默认的访问安全规则是"denyAll()"，
     * 即在默认的情况下它是关闭的，
     * 你可以注入一个标准的 SpEL 表达式到 AuthorizationServerSecurityConfigurer 这个配置中来将它开启
     * （例如使用"permitAll()"来开启可能比较合适，因为它是一个公共密钥）。
     * @author lv
     * @create 15:54 2017/12/28
     * @last modify by [lv 15:54 2017/12/28 ]
     * @param security
     * @return void
     */
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security
                .tokenKeyAccess("permitAll()")
                .checkTokenAccess("isAuthenticated()")
                .allowFormAuthenticationForClients();//允许client使用form的方式进行authentication的授权

    }

    /**
     * @Description: 用来配置客户端详情服务（ClientDetailsService），
     * 客户端详情信息在这里进行初始化，你能够把客户端详情信息写死在这里或者是通过数据库来存储调取详情信息。
     *
     * 配置客户端详情信息（Client Details)：
     *ClientDetailsServiceConfigurer (AuthorizationServerConfigurer 的一个回调配置项，见上的概述) 能够使用内存或者JDBC来实现客户端详情服务（ClientDetailsService），有几个重要的属性如下列表：
     * clientId：（必须的）用来标识客户的Id。
     * secret：（需要值得信任的客户端）客户端安全码，如果有的话。
     * scope：用来限制客户端的访问范围，如果为空（默认）的话，那么客户端拥有全部的访问范围。
     * authorizedGrantTypes：此客户端可以使用的授权类型，默认为空。
     * authorities：此客户端可以使用的权限（基于Spring Security authorities）。
     * 客户端详情（Client Details）能够在应用程序运行的时候进行更新，
     * 可以通过访问底层的存储服务（例如将客户端详情存储在一个关系数据库的表中，就可以使用 JdbcClientDetailsService）
     * 或者通过 ClientDetailsManager 接口（同时你也可以实现 ClientDetailsService 接口）来进行管理。
     * @author lv
     * @create 16:00 2017/12/28
     * @last modify by [lv 16:00 2017/12/28 ]
     * @param clients
     * @return void
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.withClientDetails(clientDetailsService);
    }


}
